webpack 实用功能

这里就不再说 webpack 打包的基础功能啦,说说一些比较实用的功能和技巧,基于 webpack3.x。

想学习 webpack 基础打包配置的童鞋可以访问上一篇文章:webpack 基础使用

提取公共代码

当前主流的开发模式都是模块化开发?使用模块化开发的时候,一般都会有其他模块依赖于公共模块的这样的一个过程,这些公共的代码模块就是我们所要提取的公共代码。

提取公共代码可以:

  • 减少代码冗余
  • 提高页面加载速度

怎么提取公共代码?

使用 webpack 自带的 webpack.optimize.CommonsChunkPlugin 插件即可(webpack4.0以后的版本已由optimization.splitChunks和optimization.runtimeChunk代替):

1
2
3
4
5
{
plugins:[
new webpack.optimize.CommonsChunkPlugin(options)
]
}

有如下可选配置:

  • options.name or options.names //名称
  • options.filename //共用代码打包后的文件名
  • options.minChunks //可以是数字(公用代码出现最小次数),函数或者无穷大
  • options.chunks //指定提取代码范围
  • options.children //是否在子模块中查找共同依赖
  • options.deepChildren //是否在所有模块中查找共同依赖
  • options.async(boolean|string) //创建异步公共代码块
  • options.minSize //在公共chunk被创建立之前,所有公共模块的最少大小。

使用场景:

  • 单页面应用
  • 单页面应用 + 第三方依赖
  • 多页面应用 + 第三方依赖 + webpack 生成代码

简单实用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var webpack = require('webpack')
var path = require('path')

module.exports = {
entry:{
'pageA': './src/pageA',
'pageB': './src/pageB',
'vendor': ['jquery']
},
output:{
path: path.resolve(__dirname,'./dist'),
filename: '[name].bundle.js',
chunkFilename:'[name].chunk.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
mikChunks: 2,
chunks: ['pageA','pageB']//提取pageA和pageB的公共代码模块到common.js
}),
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest'],
mikChunks: Infinity
})
]
}

代码分割和懒加载

根据实际业务需求将代码进行分割,然后在合适的时候在将其加载进入文档中(按需加载)。webpack 需要做的工作有两方面:第一是要把各个需要按需加载的模块切片成不同的文件(每个模块存在一个文件里,这个文件叫做一个切片)而不是打包到一个文件里。第二是在我们需要时引入。这样,相应地,就需要我们做两方面工作:第一是告诉 webpack 切片文件存放的位置。第二就是告诉 webpack 什么时候引入切片。

使用场景:

  • 分离业务代码 和 第三方依赖
  • 分离业务代码 和 业务公共代码 和 第三方依赖
  • 分离首次加载 和 访问后加载

简单使用(require.ensure 的方式,只引入需要内部再执行):

webpack.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
var webpack = require('webpack')
var path = require('path')

module.exports = {
entry:{
'pageA': './src/pageA',
},
output:{
path: path.resolve(__dirname,'./dist'),
filename: '[name].bundle.js',
chunkFilename:'[name].chunk.js'
}
}

pageA.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

// import './subPageA'
// import './subPageB'
// import * as _ from 'lodash' //全换成下面的代码分割模式按需加载

require.include('./moduleA')

var page = "subPageA"

if(page === "subPageA"){
require.ensure(['./subPageA'],function(){
var subPageA = require('./subPageA'); //如果这里不继续引用,内部的代码不会执行
},'subPageA')

}else{
require.ensure(['./subPageB'],function(){
var subPageB = require('./subPageB');
},'subPageB')
}


require.ensure(['lodash'],function(){
var _ = require('lodash');
_.join(['1','2'],'3');
},'vendor')


export default 'pageA'

简单使用(动态 import 的方式,自动执行,import().then()内部封装了promise):

pageA.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//这里require.include() 可以将共同依赖的 moduleA 单独抽离在pageA中,而不是在subPageA和subPageB中都存在一份
//subPageA和subPageB一样还需要 import './moduleA'
require.include('./moduleA')

var page = "subPageA"

if(page === "subPageA"){
import(/* webpackChunkName: 'subPageA' */'./subPageA').then(function(subPageA){ //使用webpack3的新功能魔法注释
console.log(subPageA)
})

}else{
import(/* webpackChunkName: 'subPageA' */'./subPageB').then(function(subPageB){ //两个名称一样,则会合并打包
console.log(subPageB)
})
}

require.ensure(['lodash'],function(){
var _ = require('lodash');
_.join(['1','2'],'3');
},'vendor')


export default 'pageA'

如上打包后结果为:

webpack

简单使用(将moduleA剥离pageA异步加载)

webpack.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var webpack = require('webpack')
var path = require('path')
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry:{
'pageA': './src/pageA',
'pageB': './src/pageB',
'vendor': ['lodash']
},
output:{
path: path.resolve(__dirname,'./dist'),
publicPath: './dist/',
filename: '[name].bundle.js',
chunkFilename:'[name].chunk.js'
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html', //入口文件
template: './index.html', //模板
chunks:['manifest','vendor','async-common'], //该html内引用的 chunks js文件
}),
new webpack.optimize.CommonsChunkPlugin({
names: ['manifest','vendor'],
mikChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
async: 'async-common', //将subPageA和subPageB共同依赖的moduleA异步打包成async-common.bundle.js
children: true,
mikChunks: 2
})
]
}

pageA.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import * as _ from 'lodash'  //改为同步加载,一般第三方库都是同步加载就行

var page = "subPageA"

if(page === "subPageA"){
import(/* webpackChunkName: 'subPageA' */'./subPageA').then(function(subPageA){ //使用webpack3的新功能魔法注释
console.log(subPageA)
})

}else{
import(/* webpackChunkName: 'subPageB' */'./subPageB').then(function(subPageB){ //两个名称一样,则会合并打包
console.log(subPageB)
})
}

export default 'pageA'

pageB.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// import './subPageA'
// import './subPageB'
import * as _ from 'lodash'

var page = "subPageB"

if(page === "subPageB"){
import(/* webpackChunkName: 'subPageA' */'./subPageA').then(function(subPageA){ //使用webpack3的新功能魔法注释
console.log(subPageA)
})

}else{
import(/* webpackChunkName: 'subPageB' */'./subPageB').then(function(subPageB){ //两个名称一样,则会合并打包
console.log(subPageB)
})
}

export default 'pageB'

subPageA.js

1
2
3
import './moduleA'
console.log("this is subPageA")
export default 'subPageA'

subPageB.js

1
2
import './moduleA'
export default 'subPageB'

moduleA.js

1
export default 'moduleA'

Tree Shaking

即移除项目中没有使用的 js 和 css

实用场景:

  • 常规优化
  • 引入第三方库的某个功能

js Tree Shaking:

1
2
3
4
5
6
7
8
9

var webpack = require('webpack')
module.exports = {
...
plugins: [
//移除本地没有使用的JS,移除第三方库多余的js需要引入相对应的插件
new webpack.optimize.UglifyJsPlugin(),
]
}

css Tree Shaking:

使用第三方插件:

npm install purifycss-webpack glob-all --save-dev

简单使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var webpack = require('webpack')
var path = require('path')
var PurifyCSS = require('purifycss-webpack')
var glob = require('glob-all')

var ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');

module.exports = {
entry:{
'app': './src/app.js'
},
output:{
path: path.resolve(__dirname,'./dist'),
publicPath: './dist/',
filename: '[name].bundle.js',
chunkFilename:'[name].chunk.js'
},
module:{

},
plugins: [
new ExtractTextWebpackPlugin({
filename: '[name].min.css',
allChunks: false
// allChunks默认false,只打包初始化的css,异步加载的css不会打包
}),
nwe PurifyCSS({ //可以和ExtractTextWebpackPlugin结合使用,但是必须放后面
paths: glob.sync([
path.join()(__dirname,'./*.html'), //定位到文件
path.join()(__dirname,'./src/*.js'),
])
}),
//移除本地没有使用的JS,移除第三方库多余的js需要引入相对应的插件
new webpack.optimize.UglifyJsPlugin(),
]
}

代理远程接口 Proxy

安装: npm install webpack-dev-server --save-dev

由于已经出了 webpack4.0,webpack-dev-server 必须安装3.0版本以下的才能适配

然后在 webpack.config.js 中加入配置 devServer:

1
2
3
devServer:{
port: 9001
},

在 package.json 中加入运行命令

1
2
3
"scripts": {
"server": "webpack-dev-server --open"
},

运行 npm run server 即可测试是否成功。

然后 通过配置 http-proxy-middleware 即可代理远程接口,常用的参数有:

  • target 设置访问地址重定向到某个地址
  • changeOrigin 改变源到url,在虚拟主机上非常有用,
  • headers 增加 http 请求头
  • logLevel 帮助调试,在命令行工具中显示代理情况和信息
  • pathRewrite 帮助重定向远程接口请求

简单实用:

1
2
3
4
5
6
7
8
9
10
devServer:{
port: 9001,
proxy:{
'/api':{
target:'https://m.weibo.cn',
changeOrigin: true
}
},
//histroyApiFallback: true //单页面应用,路径都重定向到首页,还可以配置 rewrites 规则
},



完~